package com.fengye.security.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fengye.security.security.filter.RestAuthenticationFilter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import java.util.Collection;
import java.util.Map;

/**
 * @author fengyexjtu@126.com
 * @date 2022年04月11日 8:41 PM
 */
@RequiredArgsConstructor
@Slf4j
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    private final ObjectMapper objectMapper;

    /**
     * 所有 url 的请求都要启用过滤器链 - 配置服务的安全功能
     * <p>
     * <p>
     * /api/test 只要认证就可以访问
     * /api/** 剩下的/api 下的接口必须拥有 ROLE_ADMIN 权限才能访问
     * <p>
     * 启用 httpBasic 认证,form 认证禁用
     *
     * @param http HttpSecurity
     * @author fengyexjtu@126.com
     * @date 2022/4/11 8:49 PM
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // formLogin
//                .formLogin(form -> form.loginPage("/login")
//                        .usernameParameter("username") // 表单用户名参数
//                        .passwordParameter("password") // 表单密码参数
//                        .successHandler(jsonAuthenticationSuccessHandler())
//                        .failureHandler(jsonAuthenticationFailureHandler())
//                        .permitAll())

                // 权限控制
                .authorizeRequests(req -> {
                    req.antMatchers("/api/**").hasRole("USER");
                    req.antMatchers("/admin/**").hasRole("ADMIN");
                    req.antMatchers("/authorize/**").permitAll();
                    req.anyRequest().authenticated();
                })

                // 添加 filter,并替换
                .addFilterAt(restAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)

                // csrf token
//                .csrf(Customizer.withDefaults())
                // 自定义下列 url 忽略 csrf 验证
                .csrf(csrf -> csrf.ignoringAntMatchers("/api/**", "/admin/**", "/authorize/**"))

                // httpBasic认证
//                .httpBasic(Customizer.withDefaults())

                // rememberMe
//                .rememberMe(rememberMe ->
//                        rememberMe
//                                .tokenValiditySeconds(30 * 24 * 3600)
//                                .rememberMeParameter("remember-meq")
//                                .rememberMeCookieName("somethingToRememberMeCookie")
//                )

                // logout 注销
//                .logout(logout ->
//                        logout.logoutUrl("/perform_logout")
//                                .logoutSuccessHandler(jsonLogoutSuccessHandler())
//                )
        ;
    }

    /*
     * 自定义 filter 实现认证
     * @author fengyexjtu@126.com
     * @date 2022/4/13 8:58 AM
     * @return com.fengye.security.security.filter.RestAuthenticationFilter
     */
    private RestAuthenticationFilter restAuthenticationFilter() throws Exception {
        RestAuthenticationFilter filter = new RestAuthenticationFilter(objectMapper);
        filter.setAuthenticationSuccessHandler(jsonAuthenticationSuccessHandler());
        filter.setAuthenticationFailureHandler(jsonAuthenticationFailureHandler());
        filter.setFilterProcessesUrl("/authorize/login");
        filter.setAuthenticationManager(authenticationManager()); // 配置用户名密码configure(AuthenticationManagerBuilder auth)
        return filter;
    }

    /**
     * 退出登录逻辑自定义
     *
     * @return org.springframework.security.web.authentication.logout.LogoutSuccessHandler
     * @author fengyexjtu@126.com
     * @date 2022/4/13 8:12 AM
     */
    private LogoutSuccessHandler jsonLogoutSuccessHandler() {
        return (request, response, authentication) -> {
            ObjectMapper objectMapper = new ObjectMapper();
            response.setStatus(HttpStatus.OK.value());
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            response.setCharacterEncoding("UTF-8");
            val data = Map.of(
                    "title", "注销成功",
                    "details", authentication.getName()
            );
            SecurityContextHolder.clearContext();
            response.getWriter().println(objectMapper.writeValueAsString(data));
        };
    }

    /**
     * 认证失败处理
     *
     * @return org.springframework.security.web.authentication.AuthenticationFailureHandler
     * @author fengyexjtu@126.com
     * @date 2022/4/13 8:13 AM
     */
    private AuthenticationFailureHandler jsonAuthenticationFailureHandler() {
        return (request, response, exception) -> {
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            response.setCharacterEncoding("UTF-8");
            val errData = Map.of(
                    "title", "认证失败",
                    "details", exception.getMessage()
            );
            response.getWriter().println(objectMapper.writeValueAsString(errData));
        };
    }


    /*
     * 参见 {@link SimpleUrlAuthenticationSuccessHandler#onAuthenticationSuccess }
     * 函数式接口 一个接口只有一个方法需要去实现.可以直接 lambda 函数去实现,而不用重新定义一个类去实现该接口
     * @author fengyexjtu@126.com
     * @date 2022/4/12 11:19 PM
     * @return org.springframework.security.web.authentication.AuthenticationSuccessHandler
     */
    private AuthenticationSuccessHandler jsonAuthenticationSuccessHandler() {
        return (request, response, authentication) -> {
            response.setStatus(HttpStatus.OK.value());
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            response.setCharacterEncoding("UTF-8");
            response.getWriter().println(objectMapper.writeValueAsString(authentication));
            log.debug("认证成功");
        };
    }

    /*
     * 处理 url 的请求不需要经过过滤器 - 用于忽略静态资源
     * @author fengyexjtu@126.com
     * @date 2022/4/12 10:00 AM
     * @param web
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/open/**");
        // 常用位置忽略安全检查
        web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations());
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("user")
                .password(passwordEncoder().encode("12345678"))
                .roles("ADMIN", "USER");
    }

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}